package level;

import entity.Entity;
import entity.Ground;
import entity.characters.Enemy;
import entity.characters.Player;
import entity.item.Item;
import entity.item.ItemType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import model.ModelFactory;

import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;

import physics.Collision;
import physics.Gravity;

/**
 * 
 * @author Jevgeni Krutov
 *
 */
public class Level {
	public List<Entity> entities; //everything except camera
	public List<Enemy> enemies = new ArrayList<Enemy>();
	public List<Item> items = new ArrayList<Item>();
	
	private static final float FLOOR_HEIGHT = 0;
	private static final int BLOCK_SIZE = 2;
	private int n, m;

	private Map<String, Cell> cells;
	private String[] playerSpawnpoint;
	private Set<String[]> enemiesSpawnpoints;
	private Set<String[]> itemsLocations;

	private Collision collisionDetector;
	private Gravity gravity;
	

	public Level(String levelPath) {
		entities = new ArrayList<Entity>();
		collisionDetector = new Collision();
		gravity = new Gravity(100f);
		
		enemiesSpawnpoints = new HashSet<String[]>();
		itemsLocations = new HashSet<String[]>();
		
		String[] lines = LevelDownloader.getLevel(levelPath);
		
		parseLevelXZDimensions(lines);
		parsePlayerSpawnpoint(lines);
		parseEnemiesSpawnpoints(lines);
		parseItemLocations(lines);
		parseWallLocations(lines);
	}
	
	private void parseLevelXZDimensions(String[] lines) {
		String[] dimensions = lines[0].split(",");
		n = Integer.parseInt(dimensions[0]);
		m = Integer.parseInt(dimensions[1]);
	}

	private void parsePlayerSpawnpoint(String[] lines) {
		playerSpawnpoint = lines[1].split(",");
	}

	private void parseEnemiesSpawnpoints(String[] lines) {
		String[] enemyUnits = lines[2].split(";");
		for(String enemyCoords : enemyUnits) {
			String coords[] = enemyCoords.split(",");
			if("".equals(coords[0])) {
				continue;
			}
			enemiesSpawnpoints.add(coords);
		}
	}

	private void parseItemLocations(String[] lines) {
		String[] items = lines[3].split(";");
		for(String item : items) {
			String coords[] = item.split(",");
			if("".equals(coords[0])) {
				continue;
			}
			itemsLocations.add(coords);
		}
	}

	private void parseWallLocations(String[] lines) {
		cells = new HashMap<String, Cell>(n * m);
		for(int i = 4; i < lines.length; i++) {
			// parse each cell with its w-coordinates specified
			String line = lines[i];
			String[] coordinates = line.split("=");
				
			String[] positions = coordinates[0].split(",");//exactly 2
			String[] values = coordinates[1].split(",");//unknown amount
				
			HashSet<Integer> s = new HashSet<Integer>();
			for(String val : values) {
				int value = Integer.parseInt(val);
				s.add(value);
			}
			cells.put(String.valueOf(positions[0]).concat(" ").concat(String.valueOf(positions[1])), new Cell(s));
		}
	}

	public void createGround() {
		Ground ground = new Ground(new Vector3f(0, -1, 0), new Vector3f(0, 0, 0));
		ground.setModel(ModelFactory.getModelInstance("assets/ground.obj"));
		entities.add(ground);
	}
	
	public void createWalls() {
		for(int n = 0; n < this.n; n++) {
			for(int m = 0; m < this.m; m++) {
				if(cells.containsKey(n + " " + m)) {
					
					Cell c = cells.get(n + " " + m);
										
					Entity tempEntity = new Entity(
							new Vector3f(- (this.n - n * BLOCK_SIZE), FLOOR_HEIGHT ,this.m - m * BLOCK_SIZE),
							new Vector3f(0,0,0))
								.setModel(ModelFactory.getModelInstance("assets/building_block.obj"));
					
					entities.add(tempEntity);
				}
			}
		}
	}
	
	public void createEnemies(Player player) {
		for(Vector4f enemySpawnPoint : getEnemiesSpawnpoints()) {
			Enemy enemy = new Enemy(
					new Vector3f(enemySpawnPoint.x, enemySpawnPoint.y, enemySpawnPoint.z),
					new Vector3f(0, 90, 0),
					ModelFactory.getModelInstance("assets/monkey.obj")
					);
			
			enemy.setPlayerToAttack(player);
			enemies.add(enemy);
		}
		entities.addAll(enemies);
	}
	
	public void createItems() {
		for(Entry<Vector4f, ItemType> itemSpawnPoint : getItemsSpawnpoints().entrySet()) {
			Vector4f coords = itemSpawnPoint.getKey();
			items.add(new Item(
					new Vector3f(coords.x, coords.y, coords.z),
					new Vector3f(0, 0, 0),
					(int) coords.w,
					itemSpawnPoint.getValue()//item type
					));
		}
		entities.addAll(items);
	}
	
	public Map<String, Cell> getCells() {
		return cells;
	}
	public int getN() {
		return n;
	}
	public int getM() {
		return m;
	}

	public Collision getCollisionDetector() {
		return collisionDetector;
	}
	
	public Gravity getGravity() {
		return gravity;
	}
	
	public Vector4f getPlayerSpawnpoint() {
		return new Vector4f(
				 - (this.n - Integer.parseInt(playerSpawnpoint[0]) * BLOCK_SIZE),
				FLOOR_HEIGHT + 1,
				this.m - Integer.parseInt(playerSpawnpoint[1]) * BLOCK_SIZE,
				Integer.parseInt(playerSpawnpoint[2]));
	}
	
	private Set<Vector4f> getEnemiesSpawnpoints() {
		Set<Vector4f> enemiesSP = new HashSet<Vector4f>();
		if(enemiesSpawnpoints.size() == 0) {
			return Collections.emptySet();
		}
		for(String esp[] : enemiesSpawnpoints) {
			enemiesSP.add(new Vector4f(
					 - (this.n - Integer.parseInt(esp[0]) * BLOCK_SIZE),
					FLOOR_HEIGHT + 1,
					this.m - Integer.parseInt(esp[1]) * BLOCK_SIZE,
					Integer.parseInt(esp[2])));
		}
		return enemiesSP;
	}
	
	private Map<Vector4f, ItemType> getItemsSpawnpoints() {
		Map<Vector4f, ItemType> itemsSP = new HashMap<Vector4f, ItemType>();
		if(itemsLocations.size() == 0) {
			return Collections.emptyMap();
		}
		for(String isp[] : itemsLocations) {
			ItemType type = null;
			
			if("m".equals(isp[0])) {
				type = ItemType.MEDKIT;
			}
			if("r".equals(isp[0])) {
				type = ItemType.ROCKET;
			}
			if("e".equals(isp[0])) {
				type = ItemType.ENERGY;
			}
			if("q".equals(isp[0])) {
				type = ItemType.EXIT;
			}
			
			itemsSP.put(
					new Vector4f(
					 - (this.n - Integer.parseInt(isp[1]) * BLOCK_SIZE),
					FLOOR_HEIGHT + 0.15f,
					this.m - Integer.parseInt(isp[2]) * BLOCK_SIZE,
					Integer.parseInt(isp[3])),
					
					type);
		}
		return itemsSP;
	}
}
